﻿Option Explicit On
Option Strict On

Imports System
Imports System.Runtime.InteropServices
Imports System.IO
Imports System.Collections.Generic
Imports System.Windows.Forms


#Region " Structures "

<StructLayout(LayoutKind.Sequential)> _
Friend Class MapiMessage
    Public reserved As Integer
    Public subject As String
    Public noteText As String
    Public messageType As String
    Public dateReceived As String
    Public conversationID As String
    Public flags As Integer
    Public originator As IntPtr
    Public recipCount As Integer
    Public recips As IntPtr
    Public fileCount As Integer
    Public files As IntPtr
End Class

<StructLayout(LayoutKind.Sequential)> _
Friend Class MapiFileDesc
    Public reserved As Integer
    Public flags As Integer
    Public position As Integer
    Public path As String
    Public name As String
    Public type As IntPtr
End Class

<StructLayout(LayoutKind.Sequential)> _
Friend Class MapiRecipDesc
    Public reserved As Integer
    Public recipClass As Integer
    Public name As String
    Public address As String
    Public eIDSize As Integer
    Public enTryID As IntPtr
End Class

#End Region

Friend Class MAPI

#Region " Constants "

    Enum howTo
        MAPI_ORIG = 0
        MAPI_TO
        MAPI_CC
        MAPI_BCC
    End Enum

#End Region

#Region " Private Members "

    ReadOnly errors() As String = New String() {"OK [0]", "User abort [1]", "General MAPI failure [2]", "MAPI login failure [3]", "Disk full [4]", "Insufficient memory [5]", "Access denied [6]", "-unknown- [7]", "Too many sessions [8]", "Too many files were specified [9]", "Too many recipients were specified [10]", "A specified attachment was not found [11]", "Attachment open failure [12]", "Attachment write failure [13]", "Unknown recipient [14]", "Bad recipient type [15]", "No messages [16]", "Invalid message [17]", "Text too large [18]", "Invalid session [19]", "Type not supported [20]", "A recipient was specified ambiguously [21]", "Message in use [22]", "Network failure [23]", "Invalid edit fields [24]", "Invalid recipients [25]", "Not supported [26]"}

    Private m_recipients As New List(Of MapiRecipDesc)
    Private m_attachments As New List(Of String)
    Private m_lastError As Integer = 0

#End Region

#Region " Constants "

    Private Const MAPI_LOGON_UI As Integer = &H1
    Private Const MAPI_DIALOG As Integer = &H8
    Private Const maxAttachments As Integer = 20

#End Region

#Region " Public Methods "

    Public Function AddRecipientTo(ByVal email As String) As Boolean
        Return AddRecipient(email, howTo.MAPI_TO)
    End Function

    Public Function SendMailPopup(ByVal strSubject As String, ByVal strBody As String) As Integer
        Return SendMail(strSubject, strBody, MAPI_LOGON_UI Or MAPI_DIALOG)
    End Function

    Public Function GetLastError() As String
        If m_lastError <= 26 Then
            Return errors(m_lastError)
        End If
        Return "MAPI error [" + m_lastError.ToString() + "]"
    End Function

#End Region

#Region " Private Methods "

    Private Function SendMail(ByVal strSubject As String, ByVal strBody As String, ByVal how As Integer) As Integer
        Dim msg As MapiMessage = New MapiMessage()
        msg.subject = strSubject
        msg.noteText = strBody
        msg.recips = GetRecipients(msg.recipCount)
        msg.files = GetAttachments(msg.fileCount)

        m_lastError = UnsafeNativeMethods.MAPISendMail(New IntPtr(0), New IntPtr(0), msg, how, 0)
        If m_lastError > 1 Then
            MsgBox("MAPISendMail failed! " + GetLastError(), MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, "MAPISendMail")
        End If

        Cleanup(msg)
        Return m_lastError
    End Function

    Private Function AddRecipient(ByVal email As String, ByVal howTo As howTo) As Boolean
        Dim recipient As MapiRecipDesc = New MapiRecipDesc()

        recipient.recipClass = CType(howTo, Integer)
        recipient.name = email
        m_recipients.Add(recipient)

        Return True
    End Function

    Private Function GetRecipients(ByRef recipCount As Integer) As IntPtr
        recipCount = 0
        If m_recipients.Count = 0 Then
            Return IntPtr.Zero
        End If

        Dim size As Integer = Marshal.SizeOf(GetType(MapiRecipDesc))
        Dim iPtr As IntPtr = Marshal.AllocHGlobal(m_recipients.Count * size)

        Dim ptr As Integer = CType(iPtr, Integer)
        Dim mapiDesc As MapiRecipDesc
        For Each mapiDesc In m_recipients
            Marshal.StructureToPtr(mapiDesc, CType(ptr, IntPtr), False)
            ptr += size
        Next

        recipCount = m_recipients.Count
        Return iPtr
    End Function

    Private Function GetAttachments(ByRef fileCount As Integer) As IntPtr
        fileCount = 0
        If m_attachments Is Nothing Then
            Return IntPtr.Zero
        End If

        If (m_attachments.Count <= 0) Or (m_attachments.Count > maxAttachments) Then
            Return IntPtr.Zero
        End If

        Dim size As Integer = Marshal.SizeOf(GetType(MapiFileDesc))
        Dim iPtr As IntPtr = Marshal.AllocHGlobal(m_attachments.Count * size)

        Dim mapiFileDesc As MapiFileDesc = New MapiFileDesc()
        mapiFileDesc.position = -1
        Dim ptr As Integer = CType(iPtr, Integer)

        Dim strAttachment As String
        For Each strAttachment In m_attachments
            mapiFileDesc.name = Path.GetFileName(strAttachment)
            mapiFileDesc.path = strAttachment
            Marshal.StructureToPtr(mapiFileDesc, CType(ptr, IntPtr), False)
            ptr += size
        Next

        fileCount = m_attachments.Count
        Return iPtr
    End Function

    Private Sub Cleanup(ByRef msg As MapiMessage)
        Dim size As Integer = Marshal.SizeOf(GetType(MapiRecipDesc))
        Dim ptr As Integer = 0

        If msg.recips <> IntPtr.Zero Then
            ptr = CType(msg.recips, Integer)
            Dim i As Integer
            For i = 0 To msg.recipCount - 1 Step i + 1
                Marshal.DestroyStructure(CType(ptr, IntPtr), GetType(MapiRecipDesc))
                ptr += size
            Next
            Marshal.FreeHGlobal(msg.recips)
        End If

        If msg.files <> IntPtr.Zero Then
            size = Marshal.SizeOf(GetType(MapiFileDesc))

            ptr = CType(msg.files, Integer)
            Dim i As Integer
            For i = 0 To msg.fileCount - 1 Step i + 1
                Marshal.DestroyStructure(CType(ptr, IntPtr), GetType(MapiFileDesc))
                ptr += size
            Next
            Marshal.FreeHGlobal(msg.files)
        End If

        m_recipients.Clear()
        m_attachments.Clear()
        m_lastError = 0
    End Sub

#End Region

End Class
